// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/builtins/builtins-async-gen.h"

#include "src/builtins/builtins-utils-gen.h"
#include "src/heap/factory-inl.h"
#include "src/objects/js-promise.h"
#include "src/objects/shared-function-info.h"

namespace v8 {
namespace internal {

    using compiler::Node;

    namespace {
        // Describe fields of Context associated with the AsyncIterator unwrap closure.
        class ValueUnwrapContext {
        public:
            enum Fields { kDoneSlot = Context::MIN_CONTEXT_SLOTS,
                kLength };
        };

    } // namespace

    Node* AsyncBuiltinsAssembler::AwaitOld(Node* context, Node* generator,
        Node* value, Node* outer_promise,
        Node* on_resolve_context_index,
        Node* on_reject_context_index,
        Node* is_predicted_as_caught)
    {
        Node* const native_context = LoadNativeContext(context);

        static const int kWrappedPromiseOffset = FixedArray::SizeFor(Context::MIN_CONTEXT_SLOTS);
        static const int kResolveClosureOffset = kWrappedPromiseOffset + JSPromise::kSizeWithEmbedderFields;
        static const int kRejectClosureOffset = kResolveClosureOffset + JSFunction::kSizeWithoutPrototype;
        static const int kTotalSize = kRejectClosureOffset + JSFunction::kSizeWithoutPrototype;

        TNode<HeapObject> base = AllocateInNewSpace(kTotalSize);
        TNode<Context> closure_context = UncheckedCast<Context>(base);
        {
            // Initialize the await context, storing the {generator} as extension.
            StoreMapNoWriteBarrier(closure_context, RootIndex::kAwaitContextMap);
            StoreObjectFieldNoWriteBarrier(closure_context, Context::kLengthOffset,
                SmiConstant(Context::MIN_CONTEXT_SLOTS));
            Node* const empty_scope_info = LoadContextElement(native_context, Context::SCOPE_INFO_INDEX);
            StoreContextElementNoWriteBarrier(
                closure_context, Context::SCOPE_INFO_INDEX, empty_scope_info);
            StoreContextElementNoWriteBarrier(closure_context, Context::PREVIOUS_INDEX,
                native_context);
            StoreContextElementNoWriteBarrier(closure_context, Context::EXTENSION_INDEX,
                generator);
            StoreContextElementNoWriteBarrier(
                closure_context, Context::NATIVE_CONTEXT_INDEX, native_context);
        }

        // Let promiseCapability be ! NewPromiseCapability(%Promise%).
        Node* const promise_fun = LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
        CSA_ASSERT(this, IsFunctionWithPrototypeSlotMap(LoadMap(promise_fun)));
        Node* const promise_map = LoadObjectField(promise_fun, JSFunction::kPrototypeOrInitialMapOffset);
        // Assert that the JSPromise map has an instance size is
        // JSPromise::kSizeWithEmbedderFields.
        CSA_ASSERT(this, WordEqual(LoadMapInstanceSizeInWords(promise_map), IntPtrConstant(JSPromise::kSizeWithEmbedderFields / kTaggedSize)));
        TNode<HeapObject> wrapped_value = InnerAllocate(base, kWrappedPromiseOffset);
        {
            // Initialize Promise
            StoreMapNoWriteBarrier(wrapped_value, promise_map);
            StoreObjectFieldRoot(wrapped_value, JSPromise::kPropertiesOrHashOffset,
                RootIndex::kEmptyFixedArray);
            StoreObjectFieldRoot(wrapped_value, JSPromise::kElementsOffset,
                RootIndex::kEmptyFixedArray);
            PromiseInit(wrapped_value);
        }

        // Initialize resolve handler
        TNode<HeapObject> on_resolve = InnerAllocate(base, kResolveClosureOffset);
        InitializeNativeClosure(closure_context, native_context, on_resolve,
            on_resolve_context_index);

        // Initialize reject handler
        TNode<HeapObject> on_reject = InnerAllocate(base, kRejectClosureOffset);
        InitializeNativeClosure(closure_context, native_context, on_reject,
            on_reject_context_index);

        VARIABLE(var_throwaway, MachineRepresentation::kTaggedPointer,
            UndefinedConstant());

        // Deal with PromiseHooks and debug support in the runtime. This
        // also allocates the throwaway promise, which is only needed in
        // case of PromiseHooks or debugging.
        Label if_debugging(this, Label::kDeferred), do_resolve_promise(this);
        Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
            &if_debugging, &do_resolve_promise);
        BIND(&if_debugging);
        var_throwaway.Bind(CallRuntime(Runtime::kAwaitPromisesInitOld, context, value,
            wrapped_value, outer_promise, on_reject,
            is_predicted_as_caught));
        Goto(&do_resolve_promise);
        BIND(&do_resolve_promise);

        // Perform ! Call(promiseCapability.[[Resolve]], undefined, « promise »).
        CallBuiltin(Builtins::kResolvePromise, context, wrapped_value, value);

        return CallBuiltin(Builtins::kPerformPromiseThen, context, wrapped_value,
            on_resolve, on_reject, var_throwaway.value());
    }

    Node* AsyncBuiltinsAssembler::AwaitOptimized(Node* context, Node* generator,
        Node* promise, Node* outer_promise,
        Node* on_resolve_context_index,
        Node* on_reject_context_index,
        Node* is_predicted_as_caught)
    {
        Node* const native_context = LoadNativeContext(context);
        CSA_ASSERT(this, IsJSPromise(promise));

        static const int kResolveClosureOffset = FixedArray::SizeFor(Context::MIN_CONTEXT_SLOTS);
        static const int kRejectClosureOffset = kResolveClosureOffset + JSFunction::kSizeWithoutPrototype;
        static const int kTotalSize = kRejectClosureOffset + JSFunction::kSizeWithoutPrototype;

        // 2. Let promise be ? PromiseResolve(« promise »).
        // Node* const promise =
        // CallBuiltin(Builtins::kPromiseResolve, context, promise_fun, value);

        TNode<HeapObject> base = AllocateInNewSpace(kTotalSize);
        TNode<Context> closure_context = UncheckedCast<Context>(base);
        {
            // Initialize the await context, storing the {generator} as extension.
            StoreMapNoWriteBarrier(closure_context, RootIndex::kAwaitContextMap);
            StoreObjectFieldNoWriteBarrier(closure_context, Context::kLengthOffset,
                SmiConstant(Context::MIN_CONTEXT_SLOTS));
            Node* const empty_scope_info = LoadContextElement(native_context, Context::SCOPE_INFO_INDEX);
            StoreContextElementNoWriteBarrier(
                closure_context, Context::SCOPE_INFO_INDEX, empty_scope_info);
            StoreContextElementNoWriteBarrier(closure_context, Context::PREVIOUS_INDEX,
                native_context);
            StoreContextElementNoWriteBarrier(closure_context, Context::EXTENSION_INDEX,
                generator);
            StoreContextElementNoWriteBarrier(
                closure_context, Context::NATIVE_CONTEXT_INDEX, native_context);
        }

        // Initialize resolve handler
        TNode<HeapObject> on_resolve = InnerAllocate(base, kResolveClosureOffset);
        InitializeNativeClosure(closure_context, native_context, on_resolve,
            on_resolve_context_index);

        // Initialize reject handler
        TNode<HeapObject> on_reject = InnerAllocate(base, kRejectClosureOffset);
        InitializeNativeClosure(closure_context, native_context, on_reject,
            on_reject_context_index);

        VARIABLE(var_throwaway, MachineRepresentation::kTaggedPointer,
            UndefinedConstant());

        // Deal with PromiseHooks and debug support in the runtime. This
        // also allocates the throwaway promise, which is only needed in
        // case of PromiseHooks or debugging.
        Label if_debugging(this, Label::kDeferred), do_perform_promise_then(this);
        Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
            &if_debugging, &do_perform_promise_then);
        BIND(&if_debugging);
        var_throwaway.Bind(CallRuntime(Runtime::kAwaitPromisesInit, context, promise,
            promise, outer_promise, on_reject,
            is_predicted_as_caught));
        Goto(&do_perform_promise_then);
        BIND(&do_perform_promise_then);

        return CallBuiltin(Builtins::kPerformPromiseThen, native_context, promise,
            on_resolve, on_reject, var_throwaway.value());
    }

    Node* AsyncBuiltinsAssembler::Await(Node* context, Node* generator, Node* value,
        Node* outer_promise,
        Node* on_resolve_context_index,
        Node* on_reject_context_index,
        Node* is_predicted_as_caught)
    {
        VARIABLE(result, MachineRepresentation::kTagged);
        Label if_old(this), if_new(this), done(this),
            if_slow_constructor(this, Label::kDeferred);

        STATIC_ASSERT(sizeof(FLAG_harmony_await_optimization) == 1);
        TNode<Word32T> flag_value = UncheckedCast<Word32T>(Load(
            MachineType::Uint8(),
            ExternalConstant(
                ExternalReference::address_of_harmony_await_optimization_flag())));
        GotoIf(Word32Equal(flag_value, Int32Constant(0)), &if_old);

        // We're running with --harmony-await-optimization enabled, which means
        // we do the `PromiseResolve(%Promise%,value)` avoiding to unnecessarily
        // create wrapper promises. Now if {value} is already a promise with the
        // intrinsics %Promise% constructor as its "constructor", we don't need
        // to allocate the wrapper promise and can just use the `AwaitOptimized`
        // logic.
        GotoIf(TaggedIsSmi(value), &if_old);
        Node* const value_map = LoadMap(value);
        GotoIfNot(IsJSPromiseMap(value_map), &if_old);
        // We can skip the "constructor" lookup on {value} if it's [[Prototype]]
        // is the (initial) Promise.prototype and the @@species protector is
        // intact, as that guards the lookup path for "constructor" on
        // JSPromise instances which have the (initial) Promise.prototype.
        Node* const native_context = LoadNativeContext(context);
        Node* const promise_prototype = LoadContextElement(native_context, Context::PROMISE_PROTOTYPE_INDEX);
        GotoIfNot(WordEqual(LoadMapPrototype(value_map), promise_prototype),
            &if_slow_constructor);
        Branch(IsPromiseSpeciesProtectorCellInvalid(), &if_slow_constructor, &if_new);

        // At this point, {value} doesn't have the initial promise prototype or
        // the promise @@species protector was invalidated, but {value} could still
        // have the %Promise% as its "constructor", so we need to check that as well.
        BIND(&if_slow_constructor);
        {
            Node* const value_constructor = GetProperty(context, value, isolate()->factory()->constructor_string());
            Node* const promise_function = LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
            Branch(WordEqual(value_constructor, promise_function), &if_new, &if_old);
        }

        BIND(&if_old);
        result.Bind(AwaitOld(context, generator, value, outer_promise,
            on_resolve_context_index, on_reject_context_index,
            is_predicted_as_caught));
        Goto(&done);

        BIND(&if_new);
        result.Bind(AwaitOptimized(context, generator, value, outer_promise,
            on_resolve_context_index, on_reject_context_index,
            is_predicted_as_caught));
        Goto(&done);

        BIND(&done);
        return result.value();
    }

    void AsyncBuiltinsAssembler::InitializeNativeClosure(Node* context,
        Node* native_context,
        Node* function,
        Node* context_index)
    {
        TNode<Map> function_map = CAST(LoadContextElement(
            native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX));
        // Ensure that we don't have to initialize prototype_or_initial_map field of
        // JSFunction.
        CSA_ASSERT(this, WordEqual(LoadMapInstanceSizeInWords(function_map), IntPtrConstant(JSFunction::kSizeWithoutPrototype / kTaggedSize)));
        STATIC_ASSERT(JSFunction::kSizeWithoutPrototype == 7 * kTaggedSize);
        StoreMapNoWriteBarrier(function, function_map);
        StoreObjectFieldRoot(function, JSObject::kPropertiesOrHashOffset,
            RootIndex::kEmptyFixedArray);
        StoreObjectFieldRoot(function, JSObject::kElementsOffset,
            RootIndex::kEmptyFixedArray);
        StoreObjectFieldRoot(function, JSFunction::kFeedbackCellOffset,
            RootIndex::kManyClosuresCell);

        TNode<SharedFunctionInfo> shared_info = CAST(LoadContextElement(native_context, context_index));
        StoreObjectFieldNoWriteBarrier(
            function, JSFunction::kSharedFunctionInfoOffset, shared_info);
        StoreObjectFieldNoWriteBarrier(function, JSFunction::kContextOffset, context);

        // For the native closures that are initialized here (for `await`)
        // we know that their SharedFunctionInfo::function_data() slot
        // contains a builtin index (as Smi), so there's no need to use
        // CodeStubAssembler::GetSharedFunctionInfoCode() helper here,
        // which almost doubles the size of `await` builtins (unnecessarily).
        TNode<Smi> builtin_id = LoadObjectField<Smi>(
            shared_info, SharedFunctionInfo::kFunctionDataOffset);
        TNode<Code> code = LoadBuiltin(builtin_id);
        StoreObjectFieldNoWriteBarrier(function, JSFunction::kCodeOffset, code);
    }

    Node* AsyncBuiltinsAssembler::CreateUnwrapClosure(Node* native_context,
        Node* done)
    {
        Node* const map = LoadContextElement(
            native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
        Node* const on_fulfilled_shared = LoadContextElement(
            native_context, Context::ASYNC_ITERATOR_VALUE_UNWRAP_SHARED_FUN);
        CSA_ASSERT(this,
            HasInstanceType(on_fulfilled_shared, SHARED_FUNCTION_INFO_TYPE));
        Node* const closure_context = AllocateAsyncIteratorValueUnwrapContext(native_context, done);
        return AllocateFunctionWithMapAndContext(map, on_fulfilled_shared,
            closure_context);
    }

    Node* AsyncBuiltinsAssembler::AllocateAsyncIteratorValueUnwrapContext(
        Node* native_context, Node* done)
    {
        CSA_ASSERT(this, IsNativeContext(native_context));
        CSA_ASSERT(this, IsBoolean(done));

        Node* const context = CreatePromiseContext(native_context, ValueUnwrapContext::kLength);
        StoreContextElementNoWriteBarrier(context, ValueUnwrapContext::kDoneSlot,
            done);
        return context;
    }

    TF_BUILTIN(AsyncIteratorValueUnwrap, AsyncBuiltinsAssembler)
    {
        Node* const value = Parameter(Descriptor::kValue);
        Node* const context = Parameter(Descriptor::kContext);

        Node* const done = LoadContextElement(context, ValueUnwrapContext::kDoneSlot);
        CSA_ASSERT(this, IsBoolean(done));

        Node* const unwrapped_value = CallBuiltin(Builtins::kCreateIterResultObject, context, value, done);

        Return(unwrapped_value);
    }

} // namespace internal
} // namespace v8
